/*************************************************************
 *Name:           Christopher Alexander
 *Email address:  alexanc2@onid.oregonstate.edu
 *Class name:     CS362-400
 *Assignment:     Team 9 - Project Test Game Initialization
 *Description:    The program will peform unit testing on the
 *                dominion.c function initializeGame(). If values
 *                for the parameters of initializeGame() are not
 *                supplied by the user, they will be generated
 *                randomly by the program.  Both valid and invalid
 *                inputs to initializeGame() will be tested.
 *************************************************************/

#include "dominion.h"
#include "interface.h"
#include <stdio.h>
#include "rngs.h"
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>

#define SEED_MIN 0
#define SEED_MAX 1000
#define PLAYERS_MIN 2
#define PLAYERS_MAX 4
#define NUM_CARD_TYPES 27
#define NUM_KCARD_TYPES 20

/*Function Prototypes*/
int* setRandomGameInitializers(struct gameState *state);
int* setKingdomCards(void);
void checkSupplyCounts(struct gameState *state, int*);
void testSuccess(struct gameState *state, int*);
void testFailure(struct gameState *state, int*);
void testInitialize(void);


//Variables that can either be defined by the user, or will be randomly set.
int numPlayers;
int numSeed;
int badSet;
int dupSet;


/*If command line args have not been used to set initial game conditions,
 *this function will randomly set those variables.
 *Params: None.
 *Return: a pointer to an array of randomly selected kingdom cards.
 */
int* setRandomGameInitializers(struct gameState *state) {
   int *kingCards;
   int i;

   //Set the number of players to be between 2-4
   numPlayers = (rand() % 3) + 2;

   //Make numPlayers a boundary case invalid number once every 50 times.
   if ((rand() % 50) == 1){
      if (rand() % 2){
         numPlayers = 1;
      }
      else{
         numPlayers = 5;
      }
   }

   //Set the seed to use in initializeGame.
   numSeed = (rand() % SEED_MAX) + 1;

   //Fill the gameState with random garbage.
   if ((rand() % 50) == 1){
      for (i = 0; i < sizeof(struct gameState); i++){
            ((char*)state)[i] = (int) (Random() * 256);
      }
   }

   //Randomly assign kingdomcards.
   kingCards = setKingdomCards();

   return kingCards;
}


/*Randomly choose 10 kingdom cards to play a game with.
 *Params: None.
 *Return: a pointer to an array of randomly selected kingdom cards.
 */
int* setKingdomCards() {
   int i, j;
   int usedCards[NUM_CARD_TYPES]; //Will keep track of cards that have been assigned.
   int* k = malloc(10 * sizeof(int));

   for(i = 0; i < 10; i++) {
      //Assign a kingdom card to this position in array.  The kingdom cards are in postions
      //7-26 in enum CARD (see dominion.h)
      k[i] = (rand() % NUM_KCARD_TYPES) + 7;
      //If the card is a duplicate, reassign until a unique card is chosen.
      while (usedCards[ (k[i]) ] == -1) {
         k[i] = (rand() % NUM_KCARD_TYPES) + 7;
      }
      //This kingdom card has now been chosen and must not be chosen again.
      usedCards[ (k[i]) ] = -1;
   }
   badSet = 0;
   dupSet = 0;

   //Occasionally make a set with a duplicate kingdom card.
   if ((rand() % 50) == 1) {
      while (1){
         i = rand() % 10;
         j = rand() % 10;
         if (i != j){
            k[i] = k[j];
            dupSet = 1;
            break;
         }
      }
   }

   //Occasionally make a set with a card that is not a kingdome card.
   if ((rand() % 50) == 1) {
      k[(rand() % 10)] = rand() % 7;
      badSet = 1;
   }

   //Occasionally make a set with an invalid card (boundary case)
   if ((rand() % 50) == 1){
      badSet = 1;
      if (rand() % 2){
         k[(rand() % 10)] = -1;
      }
      else{
         k[(rand() % 10)] = NUM_CARD_TYPES;
      }
   }
   return k;
}


/*Once the gameState has been initialized, check the quantities of
 *all the card supplys to make sre they are correct.
 *Params: state - a pointer to an initialized struct gameState.
 *        kingCards - the array of cards used by initializeGame().
 *Return: None.
 */
void checkSupplyCounts(struct gameState *state, int *kingCards) {
   int i;
   int j;
   int usedCard;

   //Check supply of curse cards.
   if (state->numPlayers == 2){
      assert(state->supplyCount[curse] == 10);
   }
   else if (state->numPlayers == 3){
      assert(state->supplyCount[curse] == 20);
   }
   else {
      assert(state->supplyCount[curse] == 30);
   }

   //Check supply of treasure cards.
   assert(state->supplyCount[copper] == (60 - (7 * state->numPlayers)));
   assert(state->supplyCount[silver] == 40);
   assert(state->supplyCount[gold] == 30);

   //Check supply of victory cards.
   if (state->numPlayers == 2){
      assert(state->supplyCount[estate] == 8);
      assert(state->supplyCount[duchy] == 8);
      assert(state->supplyCount[province] == 8);
   }
   else {
      assert(state->supplyCount[estate] == 12);
      assert(state->supplyCount[duchy] == 12);
      assert(state->supplyCount[province] == 12);
   }

   //Check supplies of kingdomCards.
   for (i = adventurer; i <= treasure_map; i++){
      usedCard = 0;

      //Find out if card was used to initialize game.
      for (j = 0; j < 10; j++){
         if (i == kingCards[j]) {
            usedCard = 1;
         }
      }
      //If card was used, check its value.
      if (usedCard == 1){
         if (i == great_hall || i == gardens) {
            if (numPlayers == 2){
               assert(state->supplyCount[i] == 8);
            }
            else {
               assert(state->supplyCount[i] == 12);
            }
         }
         else {
            assert(state->supplyCount[i] == 10);
         }
      }
      else {
         assert(state->supplyCount[i] == -1);
      }
   }
}


/*Check that initializeGame() has performed correctly by running tests
 *on the struct gameState.
 *This function tests when initializeGame() returns 0.
 *Params: state - a pointer to an initialized struct gameState.
 *        kingCards - the array of cards used by initializeGame().
 *Return: None.
 */
void testSuccess(struct gameState *state, int *kingCards){
   int i;
   int coinCount = 0;

   //The set of kingdomCards used should not be composed of curse cards,
   //treasure cards, or estate, duchy, province.
   assert(badSet == 0);

   //All card supply counts should be set properly.
   checkSupplyCounts(state, kingCards);

   //Check discardCounts for all players == 0.
   for (i = 0; i < state->numPlayers; i++){
      assert(state->discardCount[i] == 0);
   }

   //Check numActions and numBuys == 1.
   assert(state->numActions == 1);
   assert(state->numBuys == 1);

   //Check that the current player's handCount and deckCount == 5.
   assert(state->handCount[state->whoseTurn] == 5);
   assert(state->deckCount[state->whoseTurn] == 5);

   //Check that the current player's hand contains only coppers and estates.
   for (i = 0; i < 5; i++) {
      assert( (state->hand[state->whoseTurn][i] == copper) ||
             (state->hand[state->whoseTurn][i] == estate) );
   }

   //Check that the current player's coin count is correct.
   for (i = 0; i < 5; i++){
      if (state->hand[state->whoseTurn][i] == copper){
         coinCount++;
      }
   }
   assert(state->coins == coinCount);
}


/*Check that initializeGame() has performed correctly by running tests
 *on the struct gameState.
 *This function tests when initializeGame() returns -1.
 *Params: state - a pointer to an initialized struct gameState.
 *        kingCards - the array of cards used by initializeGame().
 *Return: None.
 */
void testFailure(struct gameState *state, int *kingCards){
   int i, j;

   //If numPlayers is invalid, then initializeGame() should have
   //failed when testing numPlayers, and state->numPlayers will
   //not have been set.  Testing that here.
   if (numPlayers < PLAYERS_MIN || numPlayers > PLAYERS_MAX){
      assert(numPlayers != state->numPlayers);
   }
   //Else numPlayers is valid, and we move onto the next test.
   else {
      assert(numPlayers == state->numPlayers);

      //If there was an error with the set of kingdomCards, then
      //initializeGame will not have reached code that sets the
      //supplyCounts. We will test silver and gold here.
      if (dupSet == 1 || badSet == 1){
         assert(state->supplyCount[silver] != 40);
         assert(state->supplyCount[gold] != 30);
      }
      //Else the kingdomCards set is ok, and we move onto the next test.
      else {
         assert(state->supplyCount[silver] == 40);
         assert(state->supplyCount[gold] == 30);

         //There is only one other way that initializeGame() returns -1.
         //That is if shuffle() returns -1.  In that case, numActions and
         //numBuys will not have been set to 1.  We will test that here.
         assert(state->numActions != 1);
         assert(state->numBuys != 1);
      }
   }
}


/*Set the parameters that are used by initializeGame(), call initializeGame()
 *using those parameters, test the resulting struct gameState for correctness.
 *Params: None.
 *Return: None.
 */
void testInitialize() {

   int *kingCards;
   struct gameState state;
   int *whoWins = malloc(sizeof(int) * MAX_PLAYERS);
   int returnValue;

   //Clear the game state and set parameters to use in call to initializeGame().
   memset(&state, 0, sizeof(struct gameState));
   kingCards = setRandomGameInitializers(&state);

   returnValue = initializeGame(numPlayers, kingCards, numSeed, &state);

   //Run different tests depending on the return value from initializeGame().
   if (returnValue == 0){
      testSuccess(&state, kingCards);
   }
   else {
      testFailure(&state, kingCards);
   }
}


int main (int argc, char** argv) {
   int i;
   int numGames = 5000;

   srand(time(NULL)); //Set up the RNG that I use (rand)
   PlantSeeds(-1); //Set up the RNG that functions in dominion use (rngs.c)

   //Test initializeGame()
   for (i = 0; i < numGames; i++) {
      testInitialize();
   }
   printf("All tests passed!\n\n");

   return 0;
}
